﻿using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Shapes;
using AzukiMap.Layer;
using AzukiMap.Tools.Logger;
using GeoAPI.Geometries;
using GisSharpBlog.NetTopologySuite.Geometries;
using Geometry = System.Windows.Media.Geometry;
using GeometryCollection = GisSharpBlog.NetTopologySuite.Geometries.GeometryCollection;
using LineSegment=System.Windows.Media.LineSegment;
using Point=System.Windows.Point;

namespace AzukiMap.Renderer
{
    public class VectorRendererSL : Renderer
    {
        public VectorRendererSL(MapLayer layer)
            : base(layer)
        {
        }

        internal MapLayer MapLayer
        {
            get { return _layer; }
            set { _layer = value; }
        }

        /// <summary>
        /// Draw Geometry.
        /// Use this when geometry type is not specified.
        /// </summary>
        /// <param name="id"></param>
        /// <param name="geom"></param>
        /// <param name="style"></param>
        public virtual List<FrameworkElement> DrawGeometry(int id, IGeometry geom, IVectorStyle style)
        {
            var geoms = new List<FrameworkElement>();
            var path = new Path();
            var group = new GeometryGroup {FillRule = FillRule.EvenOdd};

            if (!HasMapCanvas())
            {
                return geoms;
            }
            switch (geom.GeometryType)
            {
                case "Point":
                    geoms.Add(DrawGeometry(id, (IPoint) geom, style));
                    break;
                case "MultiPoint":
                    geoms.AddRange(DrawGeometry(id, (IMultiPoint) geom, style));
                    break;
                case "LineString":
                    CreatePathGeometryGroup(group, (ILineString) geom);
                    break;
                case "Polygon":
                    CreatePathGeometryGroup(group, (IPolygon) geom);
                    break;
                case "MultiLineString":
                    foreach(ILineString line in ((IMultiLineString) geom).Geometries)
                    {
                        CreatePathGeometryGroup(group, line);
                    }
                    break;
                case "MultiPolygon":
                    foreach(IPolygon poly in ((IMultiPolygon)geom).Geometries)
                    {
                        CreatePathGeometryGroup(group, poly);
                    }
                    break;
                case "GeometryCollection":
                    foreach(IGeometry g in (GeometryCollection)geom )
                    {
                        DrawGeometry(id, g, style);
                    }
                    //geoms.AddRange(DrawGeometry(id, geom, style));
                    break;
                default:
                    Logger.Error("This geometry type is not supported to draw. type: '" + geom.GeometryType + "'");
                    throw new ArgumentException("This geometry type is not supported to draw. type: '" + geom.GeometryType + "'");
            }


            if ( group.Children.Count != 0)
            {
                path.Data = group;
                geoms.Add(path);

                var center = geom.Centroid;
                if (center.X < _layer.WestEnd) center.X += _layer.WorldWidth;
                if (center.X > _layer.EastEnd) center.X -= _layer.WorldWidth;

                bool isEast = (center.X >= 0);
                PlaceToLayer(id, path, style, isEast);
            }

            // record default style
            foreach (FrameworkElement element in geoms)
            {
                element.Resources.Add("defaultStyle", style);
            }

            return geoms;
        }

        public virtual List<FrameworkElement> DrawGeometry(int id, IGeometry geom, IVectorStyle style, double size) 
        {
            if (geom.GeometryType != "Point" && geom.GeometryType != "MultiPoint")
            {
                return DrawGeometry(id, geom, style);
            }

            var geoms = new List<FrameworkElement>();

            if (!HasMapCanvas())
            {
                return geoms;
            }
            switch (geom.GeometryType)
            {
                case "Point":
                    geoms.Add(DrawGeometry(id, (IPoint) geom, style, size));
                    break;
                case "MultiPoint":
                    geoms.AddRange(DrawGeometry(id, (IMultiPoint) geom, style, size));
                    break;
                default:
                    Logger.Error("This geometry type is not supported to draw. type: '" + geom.GeometryType + "'");
                    throw new ArgumentException("This geometry type is not supported to draw. type: '" + geom.GeometryType + "'");
            }
            
            // record default style
            foreach (FrameworkElement element in geoms)
            {
                element.Resources.Add("defaultStyle", style);
            }

            return geoms;
        }

        private FrameworkElement DrawGeometry(int id, IPoint point, IVectorStyle style)
        {
            Logger.All(
                "VectorRendererSL.DrawGeometry(Geometries.Point): "
                + " id = " + id
                );
            switch (style.PointType)
            {
                case PointDrawingType.Circle:
                    return DrawPointAsCircle(id, point, style);
                case PointDrawingType.Rect:
                    return DrawPointAsRect(id, point, style);
                case PointDrawingType.Bitmap:
                    return DrawPointAsBitmap(id, point, style);
                case PointDrawingType.Bar:
                    return DrawPointAsRect(id, point, style);
                default:
                    return DrawPointAsCircle(id, point, style);
            }
        }

        private FrameworkElement DrawGeometry(int id, IPoint point, IVectorStyle style, double size)
        {
            Logger.All(
                "VectorRendererSL.DrawGeometry(Geometries.Point): "
                + " id = " + id
                );
            switch (style.PointType)
            {
                case PointDrawingType.Circle:
                    return DrawPointAsCircle(id, point, style, size);
                case PointDrawingType.Rect:
                    return DrawPointAsRect(id, point, style, size);
                case PointDrawingType.Bitmap:
                    throw  new NotImplementedException();
                case PointDrawingType.Bar:
                    return DrawPointAsBar(id, point, style, size);
                default:
                    return DrawPointAsCircle(id, point, style);
            }
        }


        /// <summary>
        /// Draw MultiPoint.
        /// </summary>
        /// <param name="id"></param>
        /// <param name="multiPoint"></param>
        /// <param name="parent"></param>
        /// <param name="style"></param>
        private IEnumerable<FrameworkElement> DrawGeometry(int id, IMultiPoint multiPoint, IVectorStyle style)
        {
            var points = new List<FrameworkElement>();
            Logger.All(
                "VectorRendererSL.DrawGeometry(Geometries.MultiPoint): "
                + " id = " + id
                );

            foreach (IPoint point in multiPoint.Geometries)
            {
                points.Add(DrawGeometry(id, point, style));
            }

            return points;
        }

        private IEnumerable<FrameworkElement> DrawGeometry(int id, IMultiPoint multiPoint, IVectorStyle style, double size)
        {
            var points = new List<FrameworkElement>();
            Logger.All(
                "VectorRendererSL.DrawGeometry(Geometries.MultiPoint): "
                + " id = " + id
                );

            foreach (IPoint point in multiPoint.Geometries)
            {
                points.Add(DrawGeometry(id, point, style, size));
            }

            return points;
        }

        /// <summary>
        /// Set a style to a drawing element and place it to the layer.
        /// </summary>
        /// <param name="id"></param>
        /// <param name="element"></param>
        /// <param name="style"></param>
        void PlaceToLayer(int id, Shape element, IVectorStyle style, bool isEast)
        {
            SetVectorStyles(element, style);

            PlaceToLayer(id, element, isEast);
        } 

        void PlaceToLayer(int id, FrameworkElement element, bool isEast)
        {
            if ( _layer.Is180Connected )
            {
                if ( isEast)
                {
                    _layer.EastCanvas.Children.Add(element);
                } else
                {
                    _layer.WestCanvas.Children.Add(element);
                }
            } else
            {
                _layer.Children.Add(element);
            }
            SetElementId(element, id);
        }


        private FrameworkElement DrawPointAsCircle(int id, IPoint point, IVectorStyle style)
        {
            var ellipse = new Ellipse();
            ellipse.Width = style.PointWidth;
            ellipse.Height = style.PointHeight;
            SetPointPosition(ellipse, point, style);
            SetVectorStyles(ellipse, style);

            /*
            _layer.Children.Add(ellipse);
             */
            bool isEast = (point.X >= 0);
            PlaceToLayer(id, ellipse, isEast);

            //SetElementId(ellipse, id);

            return ellipse;
        }

        private FrameworkElement DrawPointAsCircle(int id, IPoint point, IVectorStyle style, double value)
        {
            double size = Math.Sqrt(value/Math.PI)*2;
            var ellipse = new Ellipse();
            ellipse.Width = size;
            ellipse.Height = size;
            SetPointPosition(ellipse, point, style, size);
            SetVectorStyles(ellipse, style);

            /*
            _layer.Children.Add(ellipse);
             */
            bool isEast = (point.X >= 0);
            PlaceToLayer(id, ellipse, isEast);

            //SetElementId(ellipse, id);

            return ellipse;
        }

        private FrameworkElement DrawPointAsRect(int id, IPoint point, IVectorStyle style)
        {
            var rect = new Rectangle();
            rect.Width = style.PointWidth;
            rect.Height = style.PointHeight;

            SetPointPosition(rect, point, style);
            SetVectorStyles(rect, style);

            /*
            _layer.Children.Add(rect);
             */
            bool isEast = (point.X >= 0);
            PlaceToLayer(id, rect, isEast);

            //SetElementId(rect, id);

            return rect;
        }

        private FrameworkElement DrawPointAsRect(int id, IPoint point, IVectorStyle style, double size)
        {
            var rect = new Rectangle();
            rect.Width = size;
            rect.Height = size;

            SetPointPosition(rect, point, style, size);
            SetVectorStyles(rect, style);

            /*
            _layer.Children.Add(rect);
             */
            bool isEast = (point.X >= 0);
            PlaceToLayer(id, rect, isEast);

            //SetElementId(rect, id);

            return rect;
        }

        private FrameworkElement DrawPointAsBar(int id, IPoint point, IVectorStyle style, double size)
        {
            if (size < 0)
            {
                size = 0;
            }
            var rect = new Rectangle();
            rect.Width = style.PointWidth;
            try
            {
                rect.Height = size;
            }
            catch (Exception ex)
            {
            }

            SetBarPosition(rect, point, style, size);
            SetVectorStyles(rect, style);

            /*
            _layer.Children.Add(rect);
             */
            bool isEast = (point.X >= 0);
            PlaceToLayer(id, rect, isEast);

            //SetElementId(rect, id);

            return rect;
        }

        private FrameworkElement DrawPointAsBitmap(int id, IPoint point, IVectorStyle style)
        {
            Image img = new Image(); //イメージコンポーネントの作成 
            img.Source = style.PointImage.Source;
            img.Width = style.PointWidth;
            img.Height = style.PointHeight;

            SetPointPosition(img, point, style);
            
            /*
            _layer.Children.Add(img);
             */
            bool isEast = (point.X >= 0);
            PlaceToLayer(id, img, isEast);

            //SetElementId(img, id);

            return img;
        }

        private void CreatePathGeometryGroup(GeometryGroup group, IPolygon poly)
        {
            // create a polygon "shell".
            PathGeometry shell = CreatePathGeometry(poly.Shell);
            group.Children.Add(shell);

            // create polygon "holes".
            for (int i = 0; i < poly.NumInteriorRings; i++)
            {
                var innerRing = (ILinearRing) poly.GetInteriorRingN(i);
                PathGeometry inner = CreatePathGeometry(innerRing);
                group.Children.Add(inner);
            }
        }

        void CreatePathGeometryGroup(GeometryGroup group, ILineString lineString)
        {
            PathGeometry pathGeom = CreatePathGeometry(lineString);
            group.Children.Add(pathGeom);
        }

        PathGeometry CreatePathGeometry(ILinearRing ring)
        {
            return CreatePathGeometry(ring.Coordinates, true);
        }

        PathGeometry CreatePathGeometry(ILineString lineString)
        {
            return CreatePathGeometry(lineString.Coordinates, false);
        }

        /// <summary>
        /// Create PathGeometry that make up polygon geometry.
        /// The return value is like below:
        /// <codes>
        ///    <PathGeometry>
        ///      <PathGeometry.Figures>
        ///       <PathFigure StartPoint="10,50">
        ///          <PathFigure.Segments>
        ///            <LineSegment Point="400,100" />
        ///            <LineSegment Point="100,140" />
        ///            <LineSegment Point="200,100" />
        ///          </PathFigure.Segments>
        ///        </PathFigure>
        ///      </PathGeometry.Figures>
        ///    </PathGeometry>
        /// </codes>
        /// </summary>
        /// <param name="coordinates"></param>
        /// <param name="isClosed"></param>
        /// <returns></returns>
        PathGeometry CreatePathGeometry(IEnumerable<ICoordinate> coordinates, bool isClosed)
        {
            var pathGeometry = new PathGeometry();
            var pathFigures = new PathFigureCollection();
            var pathFigure = new PathFigure();

            bool isFirstPoint = true;
            foreach (Coordinate coord in coordinates)
            {
                IPoint pixPoint = _layer.MapCoord.GetPixPointFromGeo(coord.X, coord.Y);
                var p = new Point(pixPoint.X, pixPoint.Y);
                if (isFirstPoint)
                {
                    pathFigure.StartPoint = p;
                    isFirstPoint = false;
                }
                else
                {
                    var lineSegment = new LineSegment {Point = p};
                    pathFigure.Segments.Add(lineSegment);
                }
            }
            if ( isClosed)
                pathFigure.IsClosed = true;

            pathFigures.Add(pathFigure);
            pathGeometry.Figures = pathFigures;

            return pathGeometry;
        }

        private void SetPointPosition(UIElement pointElement, IPoint point, IVectorStyle style)
        {
            IPoint pixPoint = _layer.MapCoord.GetPixPointFromGeo(point.X, point.Y);
            double top = pixPoint.Y + style.AdjustY - style.PointHeight / 2;
            double left = pixPoint.X + style.AdjustX - style.PointWidth / 2;
            pointElement.SetValue(Canvas.TopProperty, top);
            pointElement.SetValue(Canvas.LeftProperty, left);
        }

        private void SetPointPosition(UIElement pointElement, IPoint point, IVectorStyle style, double size)
        {
            IPoint pixPoint = _layer.MapCoord.GetPixPointFromGeo(point.X, point.Y);
            double top = pixPoint.Y + style.AdjustY - size / 2;
            double left = pixPoint.X + style.AdjustX - size / 2;
            pointElement.SetValue(Canvas.TopProperty, top);
            pointElement.SetValue(Canvas.LeftProperty, left);
        }

        private void SetBarPosition(UIElement pointElement, IPoint point, IVectorStyle style, double size)
        {
            IPoint pixPoint = _layer.MapCoord.GetPixPointFromGeo(point.X, point.Y);
            double top = pixPoint.Y + style.AdjustY - size;
            double left = pixPoint.X + style.AdjustX - style.PointWidth / 2;
            pointElement.SetValue(Canvas.TopProperty, top);
            pointElement.SetValue(Canvas.LeftProperty, left);
        }

        /// <summary
        /// Open drawing context
        /// </summary>
        public virtual void StartRendering()
        {
            // do nothing.
        }

        /// <summary>
        /// Close drawing context to draw objects.
        /// </summary>
        public virtual void FinishRendering()
        {
            // do nothing.
        }

        public static void SetVectorStyles(Shape shape, IVectorStyle style)
        {
            if ( style == null )
            {
                return;
            }
            shape.Fill = style.FillBrush;
            shape.Opacity = style.Opacity;
            shape.OpacityMask = style.OpacityMask;
            shape.Stroke = style.StrokeBrush;
            //shape.StrokeDashArray = style.StrokeDashArray;
            shape.StrokeDashCap = style.StrokeDashCap;
            shape.StrokeDashOffset = style.StrokeDashOffset;
            shape.StrokeEndLineCap = style.StrokeEndLineCap;
            shape.StrokeLineJoin = style.StrokeLineJoin;
            shape.StrokeMiterLimit = style.StrokeMiterLimit;
            shape.StrokeStartLineCap = style.StrokeStartLineCap;
            shape.StrokeThickness = style.StrokeThickness;
        }

        public static IVectorStyle GetVectorStyleFromShape(Shape shape)
        {
            var style
                = new VectorStyle
                      {
                          FillBrush = shape.Fill,
                          Opacity = shape.Opacity,
                          OpacityMask = shape.OpacityMask,
                          StrokeBrush = shape.Stroke,
                          StrokeDashCap = shape.StrokeDashCap,
                          StrokeDashOffset = shape.StrokeDashOffset,
                          StrokeEndLineCap = shape.StrokeEndLineCap,
                          StrokeLineJoin = shape.StrokeLineJoin,
                          StrokeMiterLimit = shape.StrokeMiterLimit,
                          StrokeStartLineCap = shape.StrokeStartLineCap,
                          StrokeThickness = shape.StrokeThickness
                          //style.StrokeDashArray = shape.StrokeDashArray;
                      };
            return style;
        }

        protected bool HasMapCanvas()
        {
            return (_layer != null);
        }

        void SetElementId(FrameworkElement element, int id)
        {
            element.Resources.Add("id", id);
        }

    }
}